package com.cmc6.websocket.config;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.cmc6.common.constant.BaseConstant;
import com.cmc6.common.exception.BaseBizCodeEnum;
import com.cmc6.common.model.enums.RequestCategoryEnum;
import com.cmc6.common.util.ApiResult;
import com.cmc6.websocket.model.entity.SocketDO;
import com.cmc6.websocket.model.enums.SocketTypeEnum;
import com.cmc6.websocket.service.SocketService;
import com.cmc6.websocket.service.WebSocketHelperService;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

public class MyWebSocketHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {

    /**
     * 连接成功
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) {
    }

    /**
     * socket 断开连接时
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) {

        Long socketId = ctx.channel().attr(MyChannelHandlerPool.SOCKET_ID_KEY).get(); // 获取信道中绑定的 uuid，socket连接记录的主键id

        // 会自动从channelGroup 通道组移除，所以这里的返回值一直都是 false
        //        MyChannelHandlerPool.channelGroup.remove(ctx.channel());

        SocketService socketService = SpringUtil.getBean(SocketService.class);

        socketService.offlineBySocketIdSet(Collections.singleton(socketId)); // 调用离线方法
    }

    /**
     * 有人 上线 之后的处理
     */
    private void handleSocketEvent(Long socketId, Channel channel) {
        WebSocketHelperService bean = SpringUtil.getBean(WebSocketHelperService.class);
        bean.handleSocketEvent(socketId, channel);
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {

        // 首次连接是FullHttpRequest，处理参数
        if (msg instanceof FullHttpRequest) {
            FullHttpRequest request = (FullHttpRequest)msg;
            String uri = request.uri();

            Map<String, String> paramMap = getUrlParams(uri);

            String code = paramMap.get("code");
            Byte category = Convert.toByte(paramMap.get(BaseConstant.REQUEST_HEADER_CATEGORY));

            if (category == null || StrUtil.isBlank(code)) {
                ApiResult.error(BaseBizCodeEnum.ILLEGAL_REQUEST);
            }

            RedisTemplate<String, SocketDO> redisTemplate = SpringUtil.getBean(RedisTemplate.class);

            ValueOperations<String, SocketDO> ops = redisTemplate.opsForValue();

            SocketDO socketDO;

            String regCode = WebSocketApplicationRunnerConfig.socketKey + code; // redis中的 注册code

            // 加上分布式锁
            RedissonClient redissonClient = SpringUtil.getBean(RedissonClient.class);
            RLock regLock = redissonClient.getLock(BaseConstant.PRE_REDISSON + regCode);
            regLock.lock();
            try {
                // redis中找到 该 regCode
                socketDO = ops.get(regCode);
                if (socketDO == null) {
                    ApiResult.error(BaseBizCodeEnum.ILLEGAL_REQUEST);
                }
                // 删除redis中该 regCode，配合分布式锁
                redisTemplate.delete(regCode);
            } finally {
                // 解除分布式锁
                regLock.unlock();
            }

            // 由于 存在 redis中的是 数字，在给对象赋值的时候，是从 下标为 0开始进行匹配的，所以这里要 减 1
            socketDO.setType(SocketTypeEnum.getByCode((byte)(socketDO.getType().getCode() - 1)));
            socketDO.setCategory(RequestCategoryEnum.getByCode(category)); // 类别：1 H5（网页端） 2 APP（移动端） 3 PC（桌面程序） 4 微信小程序
            SocketService socketService = SpringUtil.getBean(SocketService.class);
            socketService.online(socketDO); // 调用上线方法：存储到数据库

            // 上线该用户：socket
            online(socketDO, ctx.channel());

            // 如果url包含参数，需要处理
            if (uri.contains("?")) {
                String newUri = uri.substring(0, uri.indexOf("?"));
                request.setUri(newUri);
            }
        }
        super.channelRead(ctx, msg);
    }

    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, TextWebSocketFrame textWebSocketFrame) {

        String text = textWebSocketFrame.text();
        if ("ping".equals(text)) {
            channelHandlerContext.writeAndFlush(new TextWebSocketFrame("pong"));
        }

    }

    private static Map<String, String> getUrlParams(String url) {
        Map<String, String> map = new HashMap<>(16);
        url = url.replace("?", ";");
        if (!url.contains(";")) {
            return map;
        }
        if (url.split(";").length > 0) {
            String[] arr = url.split(";")[1].split("&");
            for (String s : arr) {
                String[] split = s.split("=");
                if (split.length == 2) {
                    map.put(split[0], split[1]);
                }
            }
            return map;
        }
        return map;

    }

    private void online(SocketDO socketDO, Channel channel) {
        // 保存channel通道的附带信息，以用户的uid为标识
        channel.attr(MyChannelHandlerPool.USER_ID_KEY).set(socketDO.getUserId());
        // 绑定 uuid，就是 socket连接记录主表，主键id
        channel.attr(MyChannelHandlerPool.SOCKET_ID_KEY).set(socketDO.getId());

        boolean add = MyChannelHandlerPool.channelGroup.add(channel);

        if (add) {
            ThreadUtil.execute(() -> handleSocketEvent(socketDO.getId(), channel)); // 连接成功之后的处理;
        }
    }
}

